library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

entity processor is 
port(
	clk, reset: in std_logic;
	instruction_in: in std_logic_vector(15 downto 0);
	get_next_instruction: out std_logic;
	datapath_out: out std_logic_vector(7 downto 0));
end processor;
  
architecture behaviourial of processor is

signal instruction : std_logic_vector (15 downto 0);
signal load_ir : std_logic;
signal state : std_logic_vector (2 downto 0);
signal datapath_in : std_logic_vector (7 downto 0);
signal mux1sel : std_logic;  
signal write : std_logic; 
signal writenum : std_logic_vector (2 downto 0); 
signal readnum : std_logic_vector (2 downto 0); 
signal loada : std_logic;  
signal loadb : std_logic;  
signal mux2sel : std_logic;
signal funct : std_logic_vector (1 downto 0);  
signal loadc : std_logic;
signal mux1out : std_logic_vector(7 downto 0);
signal regfileout : std_logic_vector(7 downto 0);
signal regaout : std_logic_vector(7 downto 0);
signal regbout : std_logic_vector(7 downto 0);
signal mux2out : std_logic_vector(7 downto 0);
signal aluout : std_logic_vector(7 downto 0);
signal regcout : std_logic_vector(7 downto 0);
type REGARRAY is array (7 downto 0) of std_logic_vector(7 downto 0);
signal reg : REGARRAY;  -- the storage for the register file
begin
-- INSTRUCTION REGISTER: PURELY SEQUENTIAL
process (clk)
  begin
    if (clk = '1' and clk'event) then
      if (load_ir = '1') then
        instruction <= instruction_in;
      end if;
    end if;
end process;

-- STATE MACHINE: SEQUENTIAL WITH ASYNCHRONOUS RESET
process (clk,reset)
  variable present_state: std_logic_vector( 2 downto 0);
  variable next_state: std_logic_vector (2 downto 0);
  begin
    if (reset = '0') then
      next_state := "000";
      present_state := next_state;
      get_next_instruction <= '1';
    elsif (clk = '1' and clk'event) then  
      case (present_state) is
        when "000" => next_state := "001";                                   
        when "001" => next_state := "010";         
        when "010" => if (instruction(15) = '1') then
                        next_state := "011";
                      else
                        next_state := "000";
                      end if;
        when "011" => next_state := "100";
        when "100" => if (instruction(14) = '0') then
                        next_state := "101";
                      else
                        next_state := "000";
                      end if;
        when others =>  next_state := "000"; -- when "101"
      end case;
      present_state := next_state;
      case (present_state) is
        when "000" => get_next_instruction <= '1';
                      state <= "000";
                      load_ir <= '0';
        when "001" => load_ir <= '1';
                      get_next_instruction <= '0';
                      state <= "001";
        when "010" => state <= "010";-- 1 clock cycle
                      get_next_instruction <= '0';
                      load_ir <= '0';
        when "011" => state <= "011"; -- 2 clock cycle
                      get_next_instruction <= '0';
                      load_ir <= '0';
        when "100" => state <= "100"; -- 3 clock cycles
                      get_next_instruction <= '0';
                      load_ir <= '0';
        when others => state <= "101"; -- 4 clock cycles -- when "101"
                      get_next_instruction <= '0';
                      load_ir <= '0';
      end case;   
    end if;
end process;

-- DECODE LOGIC BLOCK: PURELY COMBINATIONAL
process (instruction,state)
  begin
  case(state) is
    -- 1st clock cyle
    when "010" => if (instruction(15) = '0') then
                      -- LOAD instruction                     
                      datapath_in <= instruction(7 downto 0); -- IMMEDIATE VALUE
                      mux1sel <= '1';  -- select right-hand path through top mux
                      write <= '1';  -- write to register file during this cycle
                      writenum <= instruction (10 downto 8); -- rd
                      readnum <= "000"; -- doesn't matter for this cycle
                      loada <= '0';  -- do not update reg a this cycle
                      loadb <= '0';  -- do not update reg b this cycle
                      mux2sel <= '0'; -- does not matter for this cycle
                      funct <= "00";  -- does not matter for this cycle
                      loadc <= '0';   -- do not update reg c during this cycle                 
                  elsif (instruction(14) = '1') then
                      -- Either MOVE or INC instruction: Read r1 into regA
                      datapath_in <= "00000000"; 
                      mux1sel <= '0';  
                      write <= '0';  
                      writenum <= "000"; 
                      readnum <= instruction(4 downto 2);
                      loada <= '1'; 
                      loadb <= '0';  
                      mux2sel <= '0'; 
                      funct <= "00";  
                      loadc <= '0';                       
                  elsif (instruction(14) = '0') then
                      -- Either ADD, SUB, AND, MUL instruction: Read r1 into regA
                      datapath_in <= "00000000"; 
                      mux1sel <= '0';  
                      write <= '0';  
                      writenum <= "000"; 
                      readnum <= instruction(7 downto 5); 
                      loada <= '1'; 
                      loadb <= '0';  
                      mux2sel <= '0'; 
                      funct <= "00";  
                      loadc <= '0';                      
                  else 
                      -- If instruction doesn't follow encoding table,
                      -- then just all signals to be 0.
                      datapath_in <= "00000000"; 
                      mux1sel <= '0';  
                      write <= '0';  
                      writenum <= "000"; 
                      readnum <= "000"; 
                      loada <= '0'; 
                      loadb <= '0';  
                      mux2sel <= '0'; 
                      funct <= "00";  
                      loadc <= '0'; 
                  end if;
      
    -- 2nd clock cycle
   when "011" => if (instruction(14) = '1') then
                  -- MOVE or INC instruction: Multiply/Add by 1 and latch into regC
                      datapath_in <= "00000000"; 
                      mux1sel <= '0'; 
                      write <= '0';  
                      writenum <= "000"; 
                      readnum <= "000"; 
                      loada <= '0';  
                      loadb <= '0'; 
                      mux2sel <= '1'; 
                      if (instruction(12) = '1') then
                        -- INC instruction: Need to add in ALU
                        funct <= "10";
                      else
                        -- MOVE instruction: Need to multiply in ALU
                        funct <= "01";
                      end if;                      
                      loadc <= '1';                 
                  elsif (instruction(14) = '0') then
                      -- Either ADD, SUB, AND, MUL: Read r2 into regB
                      datapath_in <= "00000000"; 
                      mux1sel <= '0';  
                      write <= '0';  
                      writenum <= "000"; 
                      readnum <= instruction(4 downto 2); 
                      loada <= '0';  
                      loadb <= '1';  
                      mux2sel <= '0'; 
                      funct <= "00";  
                      loadc <= '0'; 
                  else
                      -- If instruction doesn't follow encoding table,
                      -- then just all signals to be 0.
                      datapath_in <= "00000000"; 
                      mux1sel <= '0';  
                      write <= '0';  
                      writenum <= "000"; 
                      readnum <= "000"; 
                      loada <= '0'; 
                      loadb <= '0';  
                      mux2sel <= '0'; 
                      funct <= "00";  
                      loadc <= '0';
                  end if;
     
    -- 3rd clock cycle
    when "100" => if (instruction(14) = '1') then
                      -- MOVE or INC instruction: Write dataout into rd
                      datapath_in <= "00000000"; 
                      mux1sel <= '0'; 
                      write <= '1';  
                      writenum <= instruction (10 downto 8); 
                      readnum <= "000"; 
                      loada <= '0';  
                      loadb <= '0';  
                      mux2sel <= '0'; 
                      funct <= "00";  
                      loadc <= '0'; 
                  elsif (instruction(14) = '0') then
                      -- Either ADD, SUB, AND, MUL: Add/Sub/And/Mul and latch into regC
                        datapath_in <= "00000000"; 
                        mux1sel <= '0';  
                        write <= '0'; 
                        writenum <= "000"; 
                        readnum <= "000"; 
                        loada <= '0';  
                        loadb <= '0';  
                        mux2sel <= '0'; 
                        -- Note that instruction(12 downto 11) is encoded the same as its ALU operations.
                        funct <= instruction(12 downto 11);
                        loadc <= '1'; 
                  else
                      -- If instruction doesn't follow encoding table,
                      -- then just all signals to be 0.
                        datapath_in <= "00000000"; 
                        mux1sel <= '0';  
                        write <= '0';  
                        writenum <= "000"; 
                        readnum <= "000"; 
                        loada <= '0'; 
                        loadb <= '0';  
                        mux2sel <= '0'; 
                        funct <= "00";  
                        loadc <= '0';                   
                  end if;
                  
    -- 4th clock cycle 
    when "101"  =>      datapath_in <= "00000000"; 
                        mux1sel <= '0'; 
                        write <= '1';  
                        writenum <= instruction(10 downto 8); 
                        --writenum <= "000";
                        readnum <= "000"; 
                        loada <= '0';  
                        loadb <= '0';  
                        mux2sel <= '0'; 
                        funct <= "00";  
                        loadc <= '0'; 
    when others  =>     datapath_in <= "00000000"; 
                        mux1sel <= '0'; 
                        write <= '0';  
                        writenum <= "000"; 
                        readnum <= "000"; 
                        loada <= '0';  
                        loadb <= '0';  
                        mux2sel <= '0'; 
                        funct <= "00";  
                        loadc <= '0'; 
  end case;
end process;

-- DATAPATH ( FROM LAB 3)

-- Mux1 process: Type 1
process(regcout,datapath_in,mux1sel)
  begin
  case (mux1sel) is
    when '0' => mux1out <= regcout;
    when others => mux1out <= datapath_in;
  end case;
end process;

-- Register file: Type 1 & Type 2
-- Type 1: READ
process(reg,readnum)
  begin
    regfileout <= reg (conv_integer (readnum));
end process;


-- Type 2: WRITE
process(clk)
  begin
    if (clk = '1' and clk'event) then
      if (write = '1') then
        reg (conv_integer (writenum)) <= mux1out;        
      end if;   
    end if; 
end process;

-- register A
process(clk)
	begin
		if (clk = '1' and clk'event) then
			if (loada = '1') then
			regaout <= regfileout;
			end if;
		end if;
	end process;
	
-- register B
process(clk)
	begin
		if (clk = '1' and clk'event) then
			if (loadb = '1') then
			regbout <= regfileout;
			end if;
		end if;
	end process;
	
-- register C
process(clk)
	begin
		if (clk = '1' and clk'event) then
			if (loadc = '1') then
			regcout <= aluout;
			end if;
		end if;
	end process;
	
-- Datapath_out is whatever sits on regcout
datapath_out <= regcout;
	
--mux2
process(mux2sel,regbout)
	begin
		case (mux2sel) is
			when '0' => mux2out <= regbout;
			when others => mux2out <= "00000001";
		end case;
end process;
	
-- The Fun Part - ALU
process(funct,regaout,mux2out)
	variable multinum : std_logic_vector(15 downto 0);
	begin
		case (funct) is
			when "00" => aluout <= regaout AND mux2out;
			when "01" => multinum := (regaout * mux2out);
			             aluout <= multinum(7 downto 0);
			when "10" => aluout <= regaout + mux2out;
			when others => aluout <= regaout - mux2out;
		end case;
	end process;

end behaviourial;

